#include "stdafx.h"
#include "CamAudio/sound_out.h"
#include "CamAudio/buffer.h"
#include <CamLib/CamError.h>

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#define new DEBUG_NEW
#endif

// IMPLEMENT_DYNCREATE(CSoundOut, CWinThread)

CSoundOut::CSoundOut()
    : m_QueuedBuffers(0)
    , m_hPlay(nullptr)
    , m_bPlaying(false)
    , GetDataToSoundOut(nullptr)
    , m_pOwner(nullptr)
    , m_ThreadID(0)
{
    CreateThread();
    m_bAutoDelete = false;
}

CSoundOut::~CSoundOut()
{
    if (m_bPlaying)
    {
        Stop();
    }
    ::PostQuitMessage(0);
}

BOOL CSoundOut::InitInstance()
{
    m_ThreadID = ::GetCurrentThreadId();
    return TRUE;
}

BEGIN_MESSAGE_MAP(CSoundOut, CWinThread)
//{{AFX_MSG_MAP(CSoundOut)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
ON_THREAD_MESSAGE(MM_WOM_DONE, OnMM_WOM_DONE)
END_MESSAGE_MAP()

bool CSoundOut::Start(WAVEFORMATEX *format)
{
    MMRESULT mmReturn = 0;

    if (m_bPlaying || GetDataToSoundOut == nullptr || m_pOwner == nullptr)
    {
        // already recording!
        return FALSE;
    }
    else
    {
        if (format != nullptr) // was this function called with at specific format
            m_Format = *format;

        // open wavein device
        mmReturn = ::waveOutOpen(&m_hPlay, WAVE_MAPPER, &m_Format, m_ThreadID, 0, CALLBACK_THREAD);
        if (mmReturn)
        {
            waveOutErrorMsg(mmReturn, _T("in start()"));
            return FALSE;
        }
        else
        {
            m_bPlaying = true;

            // we need at least 2 -> one for playing, one for refilling (3 is better)
            for (int i = 0; i < 3; i++)
            {
                CBuffer buf(m_Format.nBlockAlign * m_BufferSize, false);
                GetDataToSoundOut(&buf, m_pOwner);
                AddOutputBufferToQueue(&buf);
            }
        }
    }
    return TRUE;
}

void CSoundOut::Stop()
{
    MMRESULT mmReturn = 0;

    if (m_bPlaying)
    {
        m_bPlaying = FALSE;
        mmReturn = ::waveOutReset(m_hPlay);
        if (mmReturn)
            waveOutErrorMsg(mmReturn, _T("in stop()"));
        TRACE("waveOutReset()\n");
    }
}

void CSoundOut::OnMM_WOM_DONE(WPARAM parm1, LPARAM parm2)
{

    MMRESULT mmReturn = 0;

    LPWAVEHDR pHdr = (LPWAVEHDR)parm2;
    mmReturn = ::waveOutUnprepareHeader(m_hPlay, pHdr, sizeof(WAVEHDR));
    if (mmReturn)
    {
        waveOutErrorMsg(mmReturn, _T("in OnWOM_DONE()"));
        return;
    }

    m_QueuedBuffers--;

    if (m_bPlaying)
    {
        CBuffer buf(pHdr->lpData, pHdr->dwBufferLength);

        // virtual function supplied by user
        GetDataToSoundOut(&buf, m_pOwner);

        if (buf.ByteLen > 0)
        {
            AddOutputBufferToQueue(&buf);
            TRACE("WOM_DONE : refill buffer\n");
            // delete old header
            delete pHdr;
            return;
        }
        else
        {
            Stop();
        }
    }

    // we are closing the waveOut handle,
    // all data must be deleted
    // this buffer was allocated in Start()
    delete pHdr->lpData;
    delete pHdr;
    TRACE("WOM_DONE : remove buffer\n");

    if (m_QueuedBuffers == 0 && m_bPlaying == false)
    {
        mmReturn = ::waveOutClose(m_hPlay);
        if (mmReturn)
            waveOutErrorMsg(mmReturn, _T("in stop()"));
        TRACE("waveOutClose()\n");
    }
}

int CSoundOut::AddOutputBufferToQueue(CBuffer *buffer)
{
    MMRESULT mmReturn = 0;

    // create the header
    LPWAVEHDR pHdr = new WAVEHDR;
    if (pHdr == nullptr)
        return 0;

    // new a buffer
    pHdr->lpData = buffer->ptr.c;
    pHdr->dwBufferLength = buffer->ByteLen;
    pHdr->dwFlags = 0;

    // prepare it
    mmReturn = ::waveOutPrepareHeader(m_hPlay, pHdr, sizeof(WAVEHDR));
    if (mmReturn)
    {
        waveOutErrorMsg(mmReturn, _T("in AddOutputBufferToQueue()"));
        return m_QueuedBuffers;
    }
    // write the buffer to output queue
    mmReturn = ::waveOutWrite(m_hPlay, pHdr, sizeof(WAVEHDR));
    if (mmReturn)
        waveOutErrorMsg(mmReturn, _T("in AddOutputBufferToQueue()"));
    // increment the number of waiting buffers
    return m_QueuedBuffers++;
}

void CSoundOut::waveOutErrorMsg(MMRESULT result, LPCTSTR addstr)
{
    // say error message
    TCHAR errorbuffer[100];
    waveOutGetErrorText(result, errorbuffer, 100);
    ErrorMsg("WAVEOUT:%x:%s %s", result, errorbuffer, addstr);
}
